# -*- coding: utf-8 -*-
# @文件名称 : HostsMaster.py
# @创建时间 : 2024-11-03
# @功能描述 : 主程序，用于管理hosts文件
#   1. 读取hosts文件内容，并提供查看hosts文件内容的功能。
#   2. 提供编辑和保存功能，对hosts文件进行增删改操作。
#   3. 提供添加域名解析的功能，将域名解析添加到hosts文件中。
#   4. 提供GitHub本地解析加速功能，提高访问 GitHub 的速度。
#   5. Hosts还原，用户可以随时将hosts文件还原为默认设置，清除所有自定义配置。
#   5. 提供备份hosts文件的功能，将当前hosts文件备份到指定路径。
#   6. 提供检查更新的功能，检查是否有新版本，如果有则提示更新，下载最新版本。
#   7. 菜单栏支持查看Windows系统信息。
### 提交前注意应修改about.py的版本信息


import tkinter as tk
from tkinter import messagebox, Entry, filedialog, Scrollbar, ttk
from tkinter.font import Font
import os
import about, get_updates, menu_control
from get_updates import check_for_updates
from utils import  center_window, get_icon_path
from menu_control import show_network_info
from wifi_history import WifiHistory
from update_dnscache import DNSCacheCleaner


# Hosts文件路径
HOSTS_PATH = "C:\\Windows\\System32\\drivers\\etc\\hosts"

# 读取hosts文件
def read_hosts():
 try:
   with open(HOSTS_PATH, "r") as file:
      return file.read()  # 读取hosts文件内容
 except Exception as e:
   messagebox.showerror("错误!", f"读取hosts文件失败: {e}")  # 读取失败时显示错误信息
   return ""

# 保存hosts文件
def save_hosts(content):
 try:
   with open(HOSTS_PATH, "w") as file:
      file.write(content)  # 将内容写入hosts文件
      return True     # 返回正确值
 except PermissionError:
   # 返回错误值(权限不足，无法修改hosts文件。需以管理员身份运行程序。)
   return False
 except Exception as e:
   # 返回错误值(保存hosts文件失败显示错误信息）
   return e

# 放置窗口
def place_window_above_root(new_window,root):  # 定义一个函数用于将新窗口置于主界面之上
 new_window.transient(root)        # 将新窗口置于主界面之上
 new_window.resizable(False, False)  # 禁止调整窗口大小
 new_window.grab_set()  # 禁止点击主界面

# 查看hosts文件
def view_hosts():
 content = read_hosts()  # 获取hosts文件内容
 view_window = tk.Toplevel(root)  # 创建新窗口显示内容
 view_window.title("查看Hosts文件")  # 设置窗口标题
 center_window(view_window, 650, 400)  # 设置窗口在屏幕中间及窗口大小

 place_window_above_root(view_window,root)        # 将新窗口置于主界面之上

 text_area = tk.Text(view_window, wrap=tk.WORD)  # 创建文本区域显示内容
 text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)  # 填充窗口
 text_area.insert(tk.INSERT, content)  # 插入内容
 text_area.config(state=tk.DISABLED)  # 设置文本区域为只读状态

 scrollbar = Scrollbar(view_window, command=text_area.yview)  # 创建滚动条
 scrollbar.pack(side=tk.RIGHT, fill=tk.Y)  # 放置滚动条
 text_area.config(yscrollcommand=scrollbar.set)  # 配置文本区域的滚动条
 view_window.iconbitmap(get_icon_path())  # 设置窗口图标

# 备份hosts文件
 def backup_hosts():

   file_path = filedialog.asksaveasfilename(
      defaultextension=".txt",
      filetypes=[("Text Files", "*.txt")],
      initialfile="hosts_backup.txt"  # 设置预制文件名
   )  # 选择备份路径

   if file_path:
      with open(file_path, "w") as file:
       file.write(content)  # 将内容写入文件
       messagebox.showinfo("Successfully!", "Hosts文件保存成功!")  # 备份成功时显示信息
      root.lift()

 backup_button = tk.Button(
   view_window, text="备份", command=backup_hosts,width=10)  # 创建备份按钮，宽度=10
 backup_button.pack(side=tk.BOTTOM, fill=tk.X,pady=3)  # 放置按钮，上下间距3像素

 def close_window():
   view_window.destroy()  # 关闭查看窗口
 close_button = tk.Button(view_window, text="关闭",
              command=close_window)  # 创建关闭按钮，自适应上面的宽度
 close_button.pack(side=tk.BOTTOM, fill=tk.X)  # 放置按钮

# 编辑hosts文件
def edit_hosts():
 content = read_hosts()  # 读取当前hosts文件的内容
 edit_window = tk.Toplevel(root)  # 创建新的窗口用于编辑
 edit_window.title("编辑Hosts文件")  # 设置窗口标题
 center_window(edit_window, 650, 400)  # 设置窗口大小和位置，居中显示

 place_window_above_root(edit_window,root)        # 将新窗口置于主界面之上

 text_area = tk.Text(edit_window, wrap=tk.WORD)  # 创建文本区域用于显示和编辑内容
 text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)  # 填充窗口
 text_area.insert(tk.INSERT, content)  # 将读取的内容插入文本区域

 scrollbar = Scrollbar(edit_window, command=text_area.yview)  # 创建滚动条
 scrollbar.pack(side=tk.RIGHT, fill=tk.Y)  # 将滚动条放置在右侧
 text_area.config(yscrollcommand=scrollbar.set)  # 配置文本区域的滚动条
 edit_window.iconbitmap(get_icon_path())  # 设置窗口图标

# 保存编辑后的内容
 def save_changes():
   new_content = text_area.get("1.0", tk.END)  # 获取文本区域的新内容
   if save_hosts(new_content):  # 保存新的内容到hosts文件
      messagebox.showinfo("Successfully!", "Hosts文件保存成功!")   # 保存成功显示信息
   else:
      messagebox.showerror(
       "错误!", "保存hosts文件失败，请以管理员身份运行程序。")  # 保存失败显示错误信息

 save_button = tk.Button(edit_window, text="保存",command=save_changes,width=10)  # 创建保存按钮
 save_button.pack(side=tk.BOTTOM, fill=tk.X,pady=3)  # 将按钮放置在底部并填充

 def close_window():
   edit_window.destroy()  # 关闭查看窗口

 close_button = tk.Button(edit_window, text="关闭",
              command=close_window)  # 创建关闭按钮
 close_button.pack(side=tk.BOTTOM, fill=tk.X)  # 放置按钮

# 添加域名解析
def add_domain():
 add_window = tk.Toplevel(root)  # 创建新窗口用于添加域名
 add_window.title("添加域名解析")  # 设置窗口标题
 center_window(add_window, 500, 100)  # 设置窗口在屏幕中间

 place_window_above_root(add_window,root)

 tk.Label(add_window, text="IP地址:").pack(side=tk.LEFT)  # 创建IP标签
 ip_entry = Entry(add_window)  # 创建IP输入框
 ip_entry.pack(side=tk.LEFT)  # 放置输入框
 tk.Label(add_window, text="域名:").pack(side=tk.LEFT)  # 创建域名标签
 domain_entry = Entry(add_window)  # 创建域名输入框
 domain_entry.pack(side=tk.LEFT)  # 放置输入框

 # 添加空白标签作为间隔
 tk.Label(add_window, text="     ").pack(side=tk.LEFT)
 
# 保存域名
 def save_domain():
   ip = ip_entry.get()  # 获取输入的IP
   domain = domain_entry.get()  # 获取输入的域名
   if ip and domain:
      content = read_hosts() + f"{ip} {domain}\n"  # 拼接新的内容
      if save_hosts(content):    # 保存新的内容
       messagebox.showinfo("Successfully!", "域名添加成功!")  # 添加成功时显示信息
      else:
       messagebox.showwarning(
         "错误!", "保存hosts文件失败，请以管理员身份运行程序!")  # 保存失败显示错误信息
      add_window.destroy()  # 关闭添加窗口
   else:
      messagebox.showerror(
       "错误!", "请输入IP和域名!")  # 输入不完整时显示警告
      place_window_above_root(add_window,root)


 save_button = tk.Button(add_window, text="添加",
             command=save_domain,width=10)  # 创建添加按钮
 save_button.pack(side=tk.LEFT)  # 放置按钮

 add_window.iconbitmap(get_icon_path())  # 设置窗口图标

# 屏蔽域名
def block_domain():
 block_window = tk.Toplevel(root)  # 创建新窗口用于屏蔽域名
 block_window.title("屏蔽域名")  # 设置窗口标题
 center_window(block_window, 400, 100)  # 设置窗口在屏幕中间及窗口大小

 place_window_above_root(block_window,root)

 tk.Label(block_window, text="域名/IP:").pack(side=tk.LEFT)  # 创建域名标签
 domain_entry = Entry(block_window,width=30)  # 创建域名输入框
 domain_entry.pack(side=tk.LEFT)  # 放置输入框

 # 添加空白标签作为间隔
 tk.Label(block_window, text="    ").pack(side=tk.LEFT)

# 添加屏蔽按钮
 def save_block():
   domain = domain_entry.get()  # 获取输入的域名
   if domain:
      content = read_hosts() + f"0.0.0.0 {domain}\n"  # 拼接新的内容
      if save_hosts(content):  # 保存新的内容
       messagebox.showinfo("Successfully!", "添加屏蔽成功!")  # 屏蔽成功时显示信息
      else:
       messagebox.showwarning(
         "错误!", "添加域名/IP失败，请以管理员身份运行程序!")  # 权限不够显示错误信息
      block_window.destroy()  # 关闭屏蔽窗口
   else:
      messagebox.showerror(
       "提示!", "请输入要屏蔽的内容!.")  # 输入不完整时显示警告

 save_button = tk.Button(block_window, text="屏蔽",
             command=save_block,width=10)  # 创建屏蔽按钮
 save_button.pack(side=tk.LEFT)  # 放置按钮

 block_window.iconbitmap(get_icon_path())  # 设置窗口图标

# 添加本地解析用于加速GitHub
def accelerate_github():
 try:
   github_entries = """
# GitHub Acceleration
140.82.113.3    github.com
140.82.113.4    api.github.com
140.82.113.5    raw.githubusercontent.com
140.82.113.6    gist.github.com
140.82.113.7    assets-cdn.github.com
140.82.113.8    avatars.githubusercontent.com
140.82.113.9    camo.githubusercontent.com
140.82.113.10   user-images.githubusercontent.com
140.82.113.11   codeload.github.com
140.82.113.12   github.map.fastly.net
140.82.113.13   github.global.ssl.fastly.net
140.82.113.14   central.github.com
140.82.113.15   desktop.githubusercontent.com
140.82.113.16   githubstatus.com
140.82.113.17   github.community
140.82.113.18   github.app
140.82.113.19   github.dev
140.82.113.20   github.net
140.82.113.21   github.com
140.82.113.22   github.io
140.82.113.23   githubusercontent.com
"""
   content = read_hosts() + github_entries  # 拼接加速条目
   if save_hosts(content):  # 保存内容
      messagebox.showinfo("Successfully!", "GitHub加速条目添加成功!")  # 添加成功时显示信息
   else:
      messagebox.showerror("错误!", "权限不足，无法修改hosts文件。请以管理员身份运行程序。")
 except Exception as e:
   messagebox.showerror("错误!", f"GitHub加速失败: {e}")  # 添加失败时显示错误信息

# 还原hosts文件
def restore_hosts():
 response = messagebox.askyesno(
   "⚠注意!", "还原Hosts文件将清除所有配置。请确保您已经查看并备份了当前的hosts文件。是否继续?")
 if response:
   try:
      default_content = """
# This is a sample hosts file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

127.0.0.1       localhost
::1             localhost
"""
      if save_hosts(default_content):   # 还原为默认内容
       messagebox.showinfo(
         "Successfully!", "Hosts文件已成功还原为默认设置!")  # 还原成功时显示信息
      else:
       messagebox.showerror(
         "错误!", "权限不足，无法修改hosts文件。请以管理员身份运行程序。")
   except Exception as e:
      messagebox.showerror(
       "错误!", f"还原Hosts文件失败: {e}")  # 还原失败时显示错误信息


# 创建主窗口
root = tk.Tk()  # 创建主窗口对象
root.title("HostsMaster (Hosts Manager Tool)")  # 设置主窗口标题
center_window(root, 400, 330)  # 设置窗口在屏幕中间及窗口大小
root.resizable(False, False)  # 禁止调整窗口大小

# 添加菜单栏
menu_bar = tk.Menu(root)

# 创建一个“网络”菜单
network_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="网络", menu=network_menu)
  # 添加菜单项
# 需要传递 root 参数
network_menu.add_command(label="查看网络配置", command=lambda:show_network_info(root))
network_menu.add_command(label="历史WiFi密码", command=lambda:WifiHistory(root))
network_menu.add_command(label="清理DNS缓存", command=lambda:DNSCacheCleaner(root))

# 添加“其他”菜单
other_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="其他", menu=other_menu)
other_menu.add_command(label="系统信息", command=menu_control.show_system_info)

# 添加“帮助”菜单
help_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="帮助", menu=help_menu)
help_menu.add_command(label="关于应用", command=about.show_about_info)
help_menu.add_separator()
help_menu.add_command(label="获取更新", command=get_updates.check_for_updates)

# 添加“退出”
menu_bar.add_command(label="退出应用", command=root.quit)

root.config(menu=menu_bar)  # 将菜单栏添加到主窗口


# 设置窗口样式
root.configure(bg="#f0f0f0")  # 设置窗口背景颜色

# 指定主界面应用logo
root.iconbitmap(get_icon_path())

# 定义一个字体(按钮引用，主界面要引用则必须放在主窗口代码后面)
button_font = Font(family="SimSun", size=10,weight='bold')

# 创建一个自定义样式的按钮
style = ttk.Style()
style.configure('RoundedButton.TButton', borderwidth=0, relief="flat",
  padding=10, background="#4CAF50", foreground="black", font=('SimSun', 12))
style.map('RoundedButton.TButton', background=[('active', '#a7a8bd')])

# 创建按钮并应用自定义样式
ttk.Button(root, text="查看Hosts文件", command=view_hosts,style='RoundedButton.TButton').pack(fill=tk.X, padx=10, pady=5)
ttk.Button(root, text="编辑Hosts文件", command=edit_hosts,style='RoundedButton.TButton').pack(fill=tk.X, padx=10, pady=5)
ttk.Button(root, text="本地域名解析", command=add_domain,style='RoundedButton.TButton').pack(fill=tk.X, padx=10, pady=5)
ttk.Button(root, text="屏蔽域名或IP", command=block_domain,style='RoundedButton.TButton').pack(fill=tk.X, padx=10, pady=5)
ttk.Button(root, text="一键加速GitHub", command=accelerate_github,style='RoundedButton.TButton').pack(fill=tk.X, padx=10, pady=5)
ttk.Button(root, text="还原Hosts文件", command=restore_hosts,style='RoundedButton.TButton').pack(fill=tk.X, padx=10, pady=5)


# 运行主循环
root.mainloop()  # 启动主循环